
package asteroids.model;
import java.util.*;

import ModelExceptions.IllegalValueException;
import ModelExceptions.IllegalVectorProductException;
import ModelExceptions.IncalculateableRangeException;
import be.kuleuven.cs.som.annotate.*;



@Value
public class Vector {

	/**
	 * This new vector with given values is created. The vector can store all doubles and infinity. 
	 * The top element is located at position 0, the next at position 1, ...
	 * @invar 	...
	 * 			|hasProperValues()
	 * @param vectorValues The values where you want to create a vector from, starting with the first, second, etc.
	 * 
	 */
	public Vector(Double... vectorValues){
		vector = new ArrayList<Double>();
		for(int i=0;i<vectorValues.length;i++){
			addAsValue(vectorValues[i],i);
		}
	}

	/**
	 * Returns the size of this vector
	 * @return The size of this vector
	 */
	@Immutable
	public int getSize(){
		return this.vector.size();
	}

	/**
	 * A method to check whether or not a value is in the vector.
	 * @param value	The value you want to check
	 * @return		True if the value is in the vector.
	 * 				|for(int i; i<getSize();i++){
	 * 				|	if(getValueAtPosition(i) == value)
	 * 				|		return true
	 * 				|	return false
	 */
	@Immutable
	public boolean hasAsValue(double value){
		return vector.contains(value);
	}

	/**
	 * Add a value to the vector at a given position
	 * @param value						The value you want to add.
	 * @param position					The position in the vector where you want to add the value.
	 * @throws IllegalValueException	Is thrown when the add was not succesfull.
	 * 									|(new this).getValueAtPosition(position) != value
	 * @post 	The value is added at the given position and the following numbers are shifted one place further.
	 * 			|(new this).getValueAtPosition(position) == value && {for each i>=position| ((new this).getValueAtPosition(i) == this.getValueAtPosition(i-1))}
	 */
	@Raw
	private void addAsValue(double value, int position) throws IllegalValueException{
		if(isInRangeToAdd(position)){
			if(canHaveAsValue(value))
				vector.add(position, value);
			else
				throw new IllegalValueException("The given value is not allowed to add.");
		}
		else{
			throw new IllegalValueException("Impossible the add at this position");
		}
	}
//
//
//	/**
//	 * Add a value to the end of the vector.
//	 * @param value The value you want to add at the end of the vector.
//	 * @effect addAsValue(value, getSize())
//	 */
//	public void addAsValue(double value) throws IllegalValueException{
//		addAsValue(value, this.getSize());
//	}
//
	/**
	 * 
	 * @param position
	 * @return	...
	 * 			| if(position<=getSize())
	 * 			|	result == true;
	 */
	private boolean isInRangeToAdd(int position){
		return(position<=getSize());
	}
//
//	/**
//	 * Remove a value at a given position
//	 * @param position 	The position in the vector where you want to remove the value.
//	 * @post			The value at the given position is deleted. The following values are moved to one position ealier in the vector.
//	 * 					| new.getValueAtPosition(position) == this.getValueAtPosition(position+1) 
//	 */
//	public void removeAsValue(int position){
//		if(position<this.getSize())
//			vector.remove(position);
//	}
//
//	public void replaceValueAtPosition(int position, double value){
//		removeAsValue(position);
//		addAsValue(position);
//	}

	/**
	 * Check whether the given value is a allowed to be in the vector.
	 * @param value	The value to check
	 * @return		True if the value is possible to be in the value.
	 * 				|return (value == Double.POSITIVE_INFINITY || value == Double.NEGATIVE_INFINITY || !Double.isNaN(value))
	 */
	public boolean canHaveAsValue(double value){
		if(value == Double.POSITIVE_INFINITY || value == Double.NEGATIVE_INFINITY || !Double.isNaN(value))
			return true;
		return false;
	}

	/**
	 * Check whether every value in the vector is allowed.
	 * @return 	true if all the elements are possible in the vector.
	 * 			| {for each i in vector.getValueAtPosition(i)| (vector.getValueAtPosition(i).canHaveAsValue == true)}
	 */
	@Immutable
	public boolean hasProperValues(){
		for(Double value: this.getVectorList()){
			if(!canHaveAsValue(value))
				return false;
		}
		return true;
	}


	
	@Immutable
	private List<Double> getVectorList(){

		List<Double> returnVector = new ArrayList<Double>();
		for(int i =0; i<this.getSize(); i++){
			returnVector.add(i, getValueAtPosition(i));
		}
		return returnVector;
	}


	/**
	 * Get the value at a given position in the vector. 
	 * @param position	The position where you want to get the value
	 * @return			The value at the given position in the vector
	 */
	@Immutable
	public double getValueAtPosition(int position){
		if(position<this.getSize())
			return this.vector.get(position);
		else{
			return Double.NaN;
		}
	}


	/**
	 * 
	 * @param vector1
	 * @param vector2
	 * @return Returns the dotproduct of two vectors of equal length. If their length isn't equal.
	 * @throws IllegalVectorProductException Is thrown when the two vectors are of inequal length
	 * @throws IncalculateableRangeException
	 */
	public static double calculateDotProduct(Vector vector1, Vector vector2) throws IllegalVectorProductException, IncalculateableRangeException{
		double result=0;
		if (!isValidVectorCombination(vector1, vector2))
			throw new IllegalVectorProductException("Those vectors aren't possible for a dotproduct, invalid combination");

		for(int counter=0; counter  <vector1.getSize(); counter++){
			double tempResult= vector1.getValueAtPosition(counter)*vector2.getValueAtPosition(counter);
			result += tempResult;
		}
		if (result == Double.POSITIVE_INFINITY||result==Double.NEGATIVE_INFINITY)
			throw new IncalculateableRangeException("Those vectors aren't possible for a dotproduct, out of range");
		return result;
	}

	private static boolean isValidVectorCombination(Vector vector1, Vector vector2){
		return (vector1.getSize() == vector2.getSize() && vector1.getSize()!=0);
	}

	/**
	 * 
	 * @param vector		The vector wherefrom you want to use some values
	 * @param dimension1		The first dimension you want to use.
	 * @param dimension2		The second dimension you want to use.
	 * @return The angle that the combination of the two dimensions make.
	 * 			|return Math.atan2(vector.getValueAtPosition(dimension2),vector.getValueAtPosition(dimension1))
	 */
	public static double getResAngleOfPerpendicularVectors(Vector vector, int dimension1,int dimension2){

		double xVectorSize = vector.getValueAtPosition(dimension1);
		double yVectorSize = vector.getValueAtPosition(dimension2);
		double angle = Math.atan2(yVectorSize, xVectorSize);
		return angle;
	}



	private final List<Double> vector;
}


